package com.atguigu.gmall.cart.service.impl;

import com.atguigu.gmall.cart.service.CartService;
import com.atguigu.gmall.common.constant.RedisConstant;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.AuthContextHolder;
import com.atguigu.gmall.common.util.JsonUtils;
import com.atguigu.gmall.feign.product.SkuInfoFeignClient;
import com.atguigu.gmall.feign.ware.WareFeignClient;
import com.atguigu.gmall.model.bo.UserIdBo;
import com.atguigu.gmall.model.cart.CartInfo;
import com.atguigu.gmall.model.order.OrderDetail;
import com.atguigu.gmall.model.product.SkuInfo;
import com.fasterxml.jackson.core.type.TypeReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service
public class CartServiceImpl implements CartService {

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    SkuInfoFeignClient skuInfoFeignClient;

    @Autowired
    WareFeignClient wareFeignClient;

    @Autowired
    ThreadPoolExecutor executor;


    @Override
    public String determinCartKey(Long userId, String tempId) {
        String cartKey = RedisConstant.CART_KEY_PREFIX;
        if (userId == null) {
            //用户没登录
            cartKey = cartKey + tempId;
        } else {
            //用户登录了
            cartKey = cartKey + userId;
        }

        return cartKey;
    }


    @Override
    public SkuInfo addToCart(String cartKey, Long skuId, Integer skuNum) {
        //购物车是否商品数量超限制
        checkCartOverflow(cartKey, skuId);


        UserIdBo idBo = AuthContextHolder.getUserIdBo();
        //提前判断是否是第一次操作临时购物车。
        boolean flag = cartIsTempAndNon(cartKey, idBo.getUserId(), idBo.getUserTempId());
        //String： key = value（Map）
        //String：map.key
        //String: map.value
        BoundHashOperations<String, String, String> cart = getCart(cartKey);
        //1、购物车中有此商品，数量更新
        if (cart.hasKey(skuId.toString())) {
            //购物车中已经有这个商品
            //1.1、拿到购物车中这个商品
            CartInfo info = getCartInfo(cartKey, skuId);
            //1.2、更新数量
            info.setSkuNum(info.getSkuNum() + skuNum);
            info.setUpdateTime(new Date()); //修改更新时间
            //1.3、更新redis数据
            saveCartInfo(cartKey, info);
            //1.4、把cartInfo转为skuInfo交给前端
            //TODO 返回的skuInfo数据有可能少了
            SkuInfo skuInfo = new SkuInfo();
            skuInfo.setId(info.getSkuId());
            skuInfo.setSkuName(info.getSkuName());
            skuInfo.setSkuDefaultImg(info.getImgUrl());
            return skuInfo;
        } else {
            //2、购物车中无此商品，则是新增
            //2.1、先查出这个商品的信息
            SkuInfo skuInfo = skuInfoFeignClient.getSkuInfo(skuId);
            //2.2、把远程的SkuInfo 转成redis购物车要保存的 CartInfo
            CartInfo cartInfo = convertSkuInfoToCartInfo(skuInfo, skuNum);
            //2.3、保存到购物车
            saveCartInfo(cartKey, cartInfo);
            //2.4、如果是第一次操作临时购物车，就给个过期时间
            if (!flag) {
                redisTemplate.expire(cartKey, 365, TimeUnit.DAYS);
            }
            return skuInfo;
        }

    }

    public void checkCartOverflow(String cartKey, Long skuId) {
        Long size = redisTemplate.opsForHash().size(cartKey);
        if (size >= RedisConstant.CART_SIZE) {
            //ResultCodeEnum resultCodeEnum
            //当前商品不是新商品
            Boolean hasKey = redisTemplate.opsForHash().hasKey(cartKey, skuId.toString());
            if (!hasKey) {
                //而且购物车没这个商品，肯定是新增
                throw new GmallException(ResultCodeEnum.CART_SIZE_OVERFLOW);
            }


        }
    }

    public boolean cartIsTempAndNon(String cartKey, Long userId, String userTempId) {
        if (userId == null) {
            return redisTemplate.hasKey(cartKey);
        }
        //注意：
        return true;
    }


    //把 商品服务查到的skuInfo，转为购物车能用的  CartInfo
    private CartInfo convertSkuInfoToCartInfo(SkuInfo skuInfo, Integer skuNum) {
        CartInfo cartInfo = new CartInfo();
        cartInfo.setSkuName(skuInfo.getSkuName());
        cartInfo.setSkuId(skuInfo.getId());
        //第一次设置价格
        cartInfo.setCartPrice(skuInfo.getPrice());  //购物车价格
        cartInfo.setSkuPrice(skuInfo.getPrice());   //实时价格
        cartInfo.setSkuNum(skuNum);
        cartInfo.setImgUrl(skuInfo.getSkuDefaultImg());
        cartInfo.setIsChecked(1); //默认是被选中的
        cartInfo.setCreateTime(new Date());
        cartInfo.setUpdateTime(new Date());

        return cartInfo;
    }

    @Override
    public BoundHashOperations<String, String, String> getCart(String cartKey) {
        return redisTemplate.boundHashOps(cartKey);
    }

    @Override
    public void saveCartInfo(String cartKey, CartInfo cartInfo) {
        BoundHashOperations<String, String, String> cart = getCart(cartKey);
        String jsonStr = JsonUtils.parseObjectToJsonStr(cartInfo);
        //保存到redis
        cart.put(cartInfo.getSkuId().toString(), jsonStr);
    }

    @Override
    public CartInfo getCartInfo(String cartKey, Long skuId) {
        BoundHashOperations<String, String, String> cart = getCart(cartKey);

        //从远程redis得到购物车中当前 skuId 对应的商品的详细json信息
        String json = cart.get(skuId.toString());
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        //把这个json转成cartinfo 对象
        CartInfo cartInfo = JsonUtils.parseJsonStrToObject(json, new TypeReference<CartInfo>() {
        });

        return cartInfo;
    }

    @Override
    public List<CartInfo> getCartList(Long userId, String tempId) {
        String cartKey = RedisConstant.CART_KEY_PREFIX;
        //1、用户如果是登录的情况下，需要把用户购物车中的数据，和临时购物车中的数据合并
        //    1）、查询临时购物车的所有数据，保存到用户购物车中    2）、清空临时购物车
        if (userId != null) {
            //用户登录了
            cartKey = cartKey + userId;
            //登录了用户的购物车数据
            //1、拿到临时购物车的所有数据
            List<CartInfo> tempList = getCartList(RedisConstant.CART_KEY_PREFIX + tempId);
            if (tempList != null && tempList.size() > 0) {
                //2、拿到每个临时购物车的数据，挨个重新调用添加到购物车方法即可
                for (CartInfo info : tempList) {
                    //把临时购物车中的每个商品添加到用户购物车
                    addToCart(cartKey, info.getSkuId(), info.getSkuNum());
                }
                //3、清空临时购物车
                clearCart(RedisConstant.CART_KEY_PREFIX + tempId);
            }

            //4、再获取用户购物车的数据
            List<CartInfo> cartList = getCartList(cartKey);
            //3、异步更新这些商品的价格
            final String cartKeyForUpdate = cartKey;

            //4、用户只要查看购物车就更新价格
            executor.submit(() -> {
                //异步更新
                asyncUpdateCartSkuPrice(cartList, cartKeyForUpdate);
            });
            return cartList;
        } else {
            //用户没登录
            cartKey = cartKey + tempId;
            final String cartKeyForUpdate = cartKey;
            List<CartInfo> cartInfos = getCartList(cartKey);
            executor.submit(() -> {
                //异步更新
                asyncUpdateCartSkuPrice(cartInfos, cartKeyForUpdate);
            });
            return cartInfos;
        }

    }

    @Override
    public void clearCart(String cartKey) {
        redisTemplate.delete(cartKey);
    }

    @Override
    public void deleteCartItem(String cartKey, Long skuId) {
        BoundHashOperations<String, String, String> cart = getCart(cartKey);
        //移除redis中的商品
        cart.delete(skuId.toString());
    }

    @Override
    public void checkedItem(String cartKey, Long skuId, Integer checked) {

        //1、先拿到这个商品
        CartInfo info = getCartInfo(cartKey, skuId);
        info.setIsChecked(checked);

        //2、保存到购物车中
        saveCartInfo(cartKey, info);


    }


    //查出指定购物车中的所有商品
    //单纯的从购物车获取所有商品
    @Override
    public List<CartInfo> getCartList(String cartKey) {
        BoundHashOperations<String, String, String> cart = getCart(cartKey);
        //1、拿到当前redis购物车中value的所有值；  HashMap
        Map<String, String> entries = cart.entries();
        // 56 --> json
        // 57 --> json
        // 58 --> json

        //2、遍历map
        List<CartInfo> infos = entries.values().stream()
                .map((jsonStr) -> {
                    //解析每个json为CartInfo
                    CartInfo cartInfo = JsonUtils.parseJsonStrToObject(jsonStr, new TypeReference<CartInfo>() {
                    });
                    return cartInfo;
                }).sorted((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime())) //正常是升序，降序需要反着来
                .collect(Collectors.toList());


        return infos;
    }

    //异步更新购物车商品价格
    public void asyncUpdateCartSkuPrice(List<CartInfo> infos, String cartKey) {
        //1、拿到所有商品id
        List<Long> skuIdList = infos.stream().map((item) -> item.getSkuId()).collect(Collectors.toList());
        //2、远程查到这些商品的最新价格
        Map<Long, BigDecimal> map =
                skuInfoFeignClient.batchGetSkuPrice(skuIdList);

        //3、更新购物车中每个商品的价格
        List<CartInfo> cartInfos = infos.stream().map(item -> {
            //修改价额
            item.setSkuPrice(map.get(item.getSkuId()));
            return item;
        }).collect(Collectors.toList());

        //4、更新完价格的所有商品重新保存到购物车中
        for (CartInfo cartInfo : cartInfos) {
            //重新存到购物车; 原子只更新价格
            // TODO  自己尝试写个Lua脚本，发给redis命令，
            // TODO 只更新这个商品的json的价格字段。否则就可能导致，商品价格更新慢而覆盖了用户更新期间的其他操作
            saveCartInfo(cartKey, cartInfo);
        }

    }

    @Override
    public List<OrderDetail> getLoginCartCheckedItem(String cartKey) {
        List<CartInfo> list = getCartList(cartKey);
        //1、在外面线程把老请求先拿到
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();

        List<OrderDetail> collect = list.stream()
                .parallel()
                .filter((item) -> item.getIsChecked() == 1)
                .map(item-> converCartInfo2OrderDetail(item))
                .collect(Collectors.toList())
                .stream()
                .sorted((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime()))
                .collect(Collectors.toList());

        return collect;
    }

    @Override
    public void clearCartItems(String cartKey, List<Long> skuIds) {
        BoundHashOperations<String, String, String> cart = getCart(cartKey);

        List<String> strings = skuIds.stream().map(item -> item.toString()).collect(Collectors.toList());
        //清除redis中这个cartKey 下的，strings hashkey的值
        cart.delete(strings.toArray());
    }

    //把cartInfo转为orderDetail
    OrderDetail converCartInfo2OrderDetail(CartInfo cartInfo) {
        OrderDetail detail = new OrderDetail();
        BeanUtils.copyProperties(cartInfo, detail);
        CompletableFuture<Void> skuPriceFuture = CompletableFuture.runAsync(() -> {
            //设置detail几个特别的属性
//            RequestContextHolder.setRequestAttributes(attributes); //给异步的thread中把当前请求再放进去
            //1、设置最新价格
            BigDecimal skuPrice = skuInfoFeignClient.getSkuPrice(cartInfo.getSkuId());
            if(skuPrice.compareTo(new BigDecimal("-99999999999")) == 0){
                //如果远程查询出问题，就使用购物车的商品最新价格
                detail.setOrderPrice(cartInfo.getSkuPrice());
            }else {
                detail.setOrderPrice(skuPrice);
            }
        }, executor);


        //库存系统崩溃了。默认都是有库存
        CompletableFuture<Void> stockFuture = CompletableFuture.runAsync(() -> {
            //2、设置是否有货；feign; http
//            RequestContextHolder.setRequestAttributes(attributes); //每开一个异步都放进去
            String stock = wareFeignClient.checkStock(cartInfo.getSkuId(), cartInfo.getSkuNum());
            detail.setHasStock(stock);
        }, executor);


        //这些任务都做完
        CompletableFuture.allOf(skuPriceFuture, stockFuture).join();

        return detail;
    }
}
